package abbot.finder;
import java.awt.Component;
import java.awt.Container;
import java.awt.Window;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import javax.swing.JDesktopPane;
import javax.swing.JInternalFrame;
import javax.swing.JMenu;
import javax.swing.SwingUtilities;
import abbot.ExitException;
import abbot.Log;
import abbot.tester.Robot;
import abbot.tester.WindowTracker;
import abbot.util.AWT;
/** Provides access to the current AWT hierarchy. */
public class AWTHierarchy implements Hierarchy {
protected static final WindowTracker tracker = WindowTracker.getTracker();
protected static final Collection EMPTY = new ArrayList();
private static Hierarchy defaultHierarchy = null;
/** Obtain a default Hierarchy. This method is provided only to support
* the deprecated <code>ComponentTester.assertFrameShowing()</code> method.
*/
public static Hierarchy getDefault() {
/*System.out.println("Using default Hierarchy: "
+ Log.getStack(Log.FULL_STACK));*/
return defaultHierarchy != null
? defaultHierarchy : new AWTHierarchy();
}
/** Set the default Hierarchy. This method is provided only to support
* the deprecated <code>ComponentTester.assertFrameShowing()</code> method.
*/
public static void setDefault(Hierarchy h) {
defaultHierarchy = h;
}
/** Returns whether the given component is reachable from any of the root
* windows. The default is to consider all components to be contained in
* the hierarchy, whether they are reachable or not (NOTE: isReachable is
* a distinctly different operation).
*/
public boolean contains(Component c) {
return true;
}
/** Properly dispose of the given Window, making it and its native
* resources available for garbage collection.
*/
public void dispose(final Window w) {
if (AWT.isAppletViewerFrame(w)) {
// Don't dispose, it must quit on its own
return;
}
Log.debug("Dispose " + w);
Window[] owned = w.getOwnedWindows();
for (int i=0;i < owned.length;i++) {
// Window.dispose is recursive; make Hierarchy.dispose recursive
// as well.
dispose(owned[i]);
}
if (AWT.isSharedInvisibleFrame(w)) {
// Don't dispose, or any child windows which may be currently
// ignored (but not hidden) will be hidden and disposed.
return;
}
// Ensure the dispose is done on the swing thread so we can catch any
// exceptions. If Window.dispose is called from a non-Swing thread,
// it will invokes the dispose action on the Swing thread but in that
// case we have no control over exceptions.
Runnable action = new Runnable() {
public void run() {
try {
// Distinguish between the abbot framework disposing a
// window and anyone else doing so.
System.setProperty("abbot.finder.disposal", "true");
w.dispose();
System.setProperty("abbot.finder.disposal", "false");
}
catch(NullPointerException npe) {
// Catch bug in AWT 1.3.1 when generating hierarchy
// events
Log.log(npe);
}
catch(ExitException e) {
// Some apps might call System.exit on WINDOW_CLOSED
Log.log("Ignoring SUT exit: " + e);
}
catch(Throwable e) {
// Don't allow other exceptions to interfere with
// disposal.
Log.warn(e);
Log.warn("An exception was thrown when disposing "
+ " the window " + Robot.toString(w)
+ ". The exception is ignored");
}
}
};
if (SwingUtilities.isEventDispatchThread()) {
action.run();
}
else {
try { SwingUtilities.invokeAndWait(action); }
catch(Exception e) { }
}
}
/** Return all root components in the current AWT hierarchy. */
public Collection getRoots() {
return tracker.getRootWindows();
}
/** Return all descendents of interest of the given Component.
This includes owned windows for Windows, children for Containers.
*/
public Collection getComponents(Component c) {
if (c instanceof Container) {
Container cont = (Container)c;
ArrayList list = new ArrayList();
list.addAll(Arrays.asList(cont.getComponents()));
// Add other components which are not explicitly children, but
// that are conceptually descendents
if (c instanceof JMenu) {
list.add(((JMenu)c).getPopupMenu());
}
else if (c instanceof Window) {
list.addAll(Arrays.asList(((Window)c).getOwnedWindows()));
}
else if (c instanceof JDesktopPane) {
// Add iconified frames, which are otherwise unreachable.
// For consistency, they are still considerered children of
// the desktop pane.
list.addAll(findInternalFramesFromIcons(cont));
}
return list;
}
return EMPTY;
}
private Collection findInternalFramesFromIcons(Container cont) {
ArrayList list = new ArrayList();
int count = cont.getComponentCount();
for (int i=0;i < count;i++) {
Component child = cont.getComponent(i);
if (child instanceof JInternalFrame.JDesktopIcon) {
JInternalFrame frame =
((JInternalFrame.JDesktopIcon)child).
getInternalFrame();
if (frame != null)
list.add(frame);
}
// OSX puts icons into a dock; handle icon manager situations here
else if (child instanceof Container) {
list.addAll(findInternalFramesFromIcons((Container)child));
}
}
return list;
}
public Container getParent(Component c) {
Container p = c.getParent();
if (p == null && c instanceof JInternalFrame) {
// workaround for bug in JInternalFrame: COMPONENT_HIDDEN is sent
// before the desktop icon is set, so
// JInternalFrame.getDesktopPane will throw a NPE if called while
// dispatching that event. Reported against 1.4.x.
JInternalFrame.JDesktopIcon icon =
((JInternalFrame)c).getDesktopIcon();
if (icon != null) {
p = icon.getDesktopPane();
}
// p = ((JInternalFrame)c).getDesktopPane();
}
return p;
}
}